package com.ruicar.afs.cloud.contract.gateway.processor;

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSON;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONConfig;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ruicar.afs.cloud.common.core.constant.CommonConstants;
import com.ruicar.afs.cloud.common.core.enums.AfsEnumUtil;
import com.ruicar.afs.cloud.common.core.util.IResponse;
import com.ruicar.afs.cloud.common.core.util.SpringContextHolder;
import com.ruicar.afs.cloud.contract.gateway.beans.ApiDefine;
import com.ruicar.afs.cloud.contract.gateway.beans.GateWayResponse;
import com.ruicar.afs.cloud.contract.gateway.beans.SysPageInfo;
import com.ruicar.afs.cloud.contract.gateway.components.ProcessorCache;
import com.ruicar.afs.cloud.contract.gateway.dto.PageInfo;
import com.ruicar.afs.cloud.contract.gateway.entity.*;
import com.ruicar.afs.cloud.common.modules.enums.SystemType;
import com.ruicar.afs.cloud.contract.gateway.mapper.GateWayDetectRecordMapper;
import com.ruicar.afs.cloud.contract.gateway.processor.value.ValueProcessor;
import com.ruicar.afs.cloud.contract.gateway.util.JavaScriptUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.Assert;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.text.MessageFormat;
import java.util.*;

/**
 * The type ProcessorHelper
 * <p>Description: </p>
 *
 * @author Fzero
 * @version 1.0
 * @date Created on 2020.07.19 18:17:16
 * @since 1.0
 */
@Slf4j
public final class ProcessorHelper {
    /**
     * Ant path matcher
     * <p>Description: </p>
     */
    private static final AntPathMatcher ANT_PATH_MATCHER = new AntPathMatcher();

    /**
     * Query string *
     *
     * @param json        json
     * @param queryString query string
     */
    private static void queryString(JSON json,String queryString){
        if(StringUtils.isNotEmpty(queryString)){
            Map<String,List<String>> queryParams = HttpUtil.decodeParams(queryString,"UTF-8");
            for (Map.Entry<String, List<String>> entry : queryParams.entrySet()) {
                json.putByPath(entry.getKey(),entry.getValue().size()>1?entry.getValue():entry.getValue().get(0));
            }
        }
    }

    /**
     * Prepare get params json
     *
     * @param request request
     * @return the json
     */
    private static JSON prepareGetParams(HttpServletRequest request){
        JSON json = JSONUtil.parse("{}");
        queryString(json,request.getQueryString());
        return json;
    }

    /**
     * Prepare post params json
     *
     * @param request request
     * @return the json
     */
    private static JSON preparePostParams(HttpServletRequest request){
        try {
            String requestBody = IoUtil.read(request.getInputStream(),StandardCharsets.UTF_8);
            requestBody = StringUtils.isNotEmpty(requestBody)?requestBody:"{}";
            JSON json =   JSONUtil.parse(requestBody);
            queryString(json,request.getQueryString());
            return json;
        }catch (Exception e){
            log.error("解析请求出错",e);
        }
       return JSONUtil.parse("{}");
    }


    /**
     * Handler *
     *
     * @param request  request
     * @param response response
     */
    public static void handler(HttpServletRequest request, HttpServletResponse response){
        try {
            JSON param = null;
            if ("get".equalsIgnoreCase(request.getMethod())) {
                param = prepareGetParams(request);
            } else if ("post".equalsIgnoreCase(request.getMethod())) {
                param = preparePostParams(request);
            } else {
                writeData(response, new GateWayResponse(HttpStatus.METHOD_NOT_ALLOWED, MediaType.APPLICATION_JSON, JSONObject.toJSONString(IResponse.fail("不支持的请求类型"))));
                return;
            }
            log.info("解析参数为:{}", param.toString());
            List<GateWayApiMapping> apiMappings = ProcessorCache.getAllGateWayMapping();
            String requestPath = request.getRequestURI();
            GateWayApiMapping matchMapping = null;
            Map<String, String> pathValues = null;
            for (GateWayApiMapping apiMapping : apiMappings) {
                if (ANT_PATH_MATCHER.match(apiMapping.getApiUri(), requestPath)) {
                    matchMapping = apiMapping;
                    pathValues = ANT_PATH_MATCHER.extractUriTemplateVariables(apiMapping.getApiUri(), requestPath);
                    for (Map.Entry<String, String> entry : pathValues.entrySet()) {
                        param.putByPath(entry.getKey(), entry.getValue());
                    }
                    log.info("匹配[{}],api名称[{}],映射配置ID[{}],配置路径[{}],路径匹配值为[{}]", requestPath, matchMapping.getApiName(), matchMapping.getId(), matchMapping.getApiUri(), pathValues);
                    break;
                }
            }
            log.info("预处理好参数为:{}", param.toString());
            if (matchMapping != null) {
                GateWayResponse gateWayResponse = ProcessorCache.getApiProcessor(matchMapping.getProcessorName()).process(param, ProcessorCache.getApiDefine(matchMapping));
                if(StringUtils.isNotEmpty(matchMapping.getPostScript())&&StringUtils.isNotEmpty(gateWayResponse.getData())){
                    gateWayResponse.setData(JavaScriptUtils.runPostScript(gateWayResponse.getData(),matchMapping.getPostScript()));
                }
                writeData(response, gateWayResponse);
            } else {
                writeData(response, new GateWayResponse(HttpStatus.NOT_FOUND, MediaType.APPLICATION_JSON, JSONObject.toJSONString(IResponse.fail("找不到配置的api地址"))));
            }
        }catch (Exception e){
            log.error("",e);
            writeData(response,new GateWayResponse(HttpStatus.INTERNAL_SERVER_ERROR, MediaType.APPLICATION_JSON, JSONObject.toJSONString(IResponse.fail(e.getMessage()))));
        }
    }


    /**
     * Write data *
     *
     * @param response response
     * @param data     data
     */
    private static void writeData(HttpServletResponse response, GateWayResponse data){
        try {
            response.setContentType(data.getMediaType().toString());
            response.setStatus(data.getStatus().value());
            response.getOutputStream().write(data.getData().getBytes(StandardCharsets.UTF_8));
            response.getOutputStream().flush();
        }catch (IOException exception){
            log.error("",exception);
        }finally {

        }
    }

    /**
     * Gen request page info page info
     *
     * @param params    params
     * @param apiDefine api define
     * @return the page info
     */
    public static PageInfo genRequestPageInfo(JSON params, ApiDefine apiDefine){
        Assert.isTrue(StringUtils.isNotEmpty(apiDefine.getGateWayApiMapping().getPageShowOrder()),"数据加载顺序配置为空");
        PageInfo requestPageInfo = new PageInfo();
        requestPageInfo.setPageSize(params.getByPath("pageSize",Long.class));
        requestPageInfo.setPageIndex(params.getByPath("pageIndex",Long.class));
        Map<String,Object> totalInfo = params.getByPath("totalInfo",Map.class);

        Assert.notNull(requestPageInfo.getPageIndex(),"接口分页参数错误");
        Assert.notNull(requestPageInfo.getPageSize(),"接口分页参数错误");
        Assert.notNull(requestPageInfo.getTotalInfo(),"接口分页参数错误");
        if(totalInfo!=null){
            totalInfo.entrySet().stream().forEach(entry->{
                requestPageInfo.getTotalInfo().put((SystemType) AfsEnumUtil.getEnum(entry.getKey(), SystemType.class),Long.valueOf(entry.getValue().toString()));
            });
        }
        for(String str:apiDefine.getGateWayApiMapping().getPageShowOrder().split(StrUtil.COMMA)){
            if(requestPageInfo.getTotalInfo().get(AfsEnumUtil.getEnum(str, SystemType.class))==null){
                requestPageInfo.getTotalInfo().put((SystemType) AfsEnumUtil.getEnum(str, SystemType.class),0L);
            }
        }
        return requestPageInfo;
    }

    /**
     * Gen request page info page info
     *
     * @param params    params
     * @param apiDefine api define
     * @return the page info
     */
    public static PageInfo genRequestEmptyPageInfo(JSON params, ApiDefine apiDefine){
        PageInfo requestPageInfo = new PageInfo();
        requestPageInfo.setPageSize(10000L);
        requestPageInfo.setPageIndex(1L);
        return requestPageInfo;
    }

    /**
     * Gen system page infos list
     *
     * @param params    params
     * @param apiDefine api define
     * @return the list
     */
    public static  List<SysPageInfo> genSystemPageInfos(JSON params, ApiDefine apiDefine){
        PageInfo requestPageInfo = genRequestPageInfo(params,apiDefine);
        return genSystemPageInfos(apiDefine,requestPageInfo);
    }

    /**
     * Gen system page infos list
     *
     * @param apiDefine       api define
     * @param requestPageInfo request page info
     * @return the list
     */
    public static  List<SysPageInfo> genSystemPageInfos(ApiDefine apiDefine,PageInfo requestPageInfo){
        List<SysPageInfo> sysPageInfoList = new ArrayList<>();
        if(requestPageInfo.getPageIndex()<2||requestPageInfo.getTotalInfo().isEmpty()){
            for(String str:apiDefine.getGateWayApiMapping().getPageShowOrder().split(StrUtil.COMMA)){
                apiDefine
                    .getTypeListMap().entrySet().stream()
                    .filter(entry-> entry.getKey()==AfsEnumUtil.getEnum(str,SystemType.class))
                    .forEach(entry->{
                        entry.getValue().forEach(systemDefine -> {
                            SysPageInfo sysPageInfo = new SysPageInfo();
                            sysPageInfo.setSkipCount(requestPageInfo.getPageIndex());
                            sysPageInfo.setLoadCount(requestPageInfo.getPageSize());
                            sysPageInfo.setSystemDefine(systemDefine);
                            sysPageInfo.setOnlyTotal(false);
                            sysPageInfoList.add(sysPageInfo);
                        });
                    });
            }
        }else{
            Long pageEndPos = requestPageInfo.getPageIndex()*requestPageInfo.getPageSize();
            Long pageStartPos = pageEndPos-requestPageInfo.getPageSize();
            pageStartPos = pageStartPos<0?0:pageStartPos;
            Long sumTotal = 0L;
            Long loadCount = requestPageInfo.getPageSize();
            for(String str:apiDefine.getGateWayApiMapping().getPageShowOrder().split(StrUtil.COMMA)){
                SysPageInfo sysPageInfo = new SysPageInfo();
                Long currentSysTotal = requestPageInfo.getTotalInfo().get(AfsEnumUtil.getEnum(str, SystemType.class));
                if(pageStartPos>=(sumTotal+currentSysTotal)){
                    sysPageInfo.setOnlyTotal(true);
                    sysPageInfo.setSkipCount(currentSysTotal);
                    sysPageInfo.setLoadCount(0L);
                }else if(pageStartPos>=sumTotal&&pageEndPos<=(sumTotal+currentSysTotal)){
                    sysPageInfo.setOnlyTotal(false);
                    sysPageInfo.setSkipCount(pageStartPos-sumTotal);
                    sysPageInfo.setLoadCount(loadCount);
                    loadCount=0L;
                }else if(pageStartPos<=sumTotal&&pageEndPos<=(sumTotal+currentSysTotal)){
                    sysPageInfo.setOnlyTotal(false);
                    sysPageInfo.setSkipCount(0L);
                    sysPageInfo.setLoadCount(loadCount-(sumTotal-pageStartPos));
                    loadCount = 0L;
                }else if(pageStartPos<=sumTotal&&pageEndPos>(sumTotal+currentSysTotal)){
                    sysPageInfo.setOnlyTotal(false);
                    sysPageInfo.setSkipCount(0L);
                    sysPageInfo.setLoadCount(currentSysTotal);
                    loadCount = loadCount-sysPageInfo.getLoadCount();
                }else if(pageStartPos>=sumTotal&&pageEndPos>=(sumTotal+currentSysTotal)){
                    sysPageInfo.setOnlyTotal(false);
                    sysPageInfo.setSkipCount(pageStartPos-sumTotal);
                    sysPageInfo.setLoadCount(currentSysTotal-(pageStartPos-sumTotal));
                    loadCount = loadCount-sysPageInfo.getLoadCount();
                }
                sysPageInfo.setSystemDefine(apiDefine.getTypeListMap().get(AfsEnumUtil.getEnum(str, SystemType.class)).get(0));
                sumTotal += requestPageInfo.getTotalInfo().get(AfsEnumUtil.getEnum(str, SystemType.class));
                sysPageInfoList.add(sysPageInfo);
            }
        }
        return  sysPageInfoList;
    }

    /**
     * Check request boolean
     *
     * @param json          json
     * @param systemMapping system mapping
     * @return the boolean
     */
    public static boolean checkRequest(JSON json, GateWaySystemMapping systemMapping){
        if(StringUtils.isEmpty(systemMapping.getSuccessValue())){
            //如果没给调用成功返回值，则不校验直接返回
            return true;
        }
        return json!=null&&StringUtils.equals(systemMapping.getSuccessValue(),json.getByPath(systemMapping.getCheckExpress(),String.class));
    }

    /**
     * Gen request uri string
     *
     * @param json       json
     * @param requestUri request uri
     * @return the string
     */
    public static String genRequestUri(JSON json,String requestUri){
        StringBuffer stringBuffer = new StringBuffer();
        int pos=0;
        while(pos<requestUri.length()){
            if(requestUri.indexOf("${",pos)<0){
                stringBuffer.append(requestUri.substring(pos));
                break;
            }
            int lastPos = pos;
            pos = requestUri.indexOf("${",lastPos);
            stringBuffer.append(requestUri.substring(lastPos,pos));
            lastPos = pos;
            pos = requestUri.indexOf("}",lastPos+2);
            String paramPath = requestUri.substring(lastPos+2,pos);
            stringBuffer.append(json.getByPath(paramPath,String.class));
            pos+=1;
        }
        return stringBuffer.toString();
    }

    /**
     * Gen json object request param json
     *
     * @param json     json
     * @param mappings mappings
     * @return the json
     */
    public static JSON genJSONObjectRequestParam(JSON json, List<GateWaySystemParamInMapping> mappings){
        JSON paramsJson = JSONUtil.parse("{}");
        mappings.stream()
                .filter(mapping -> StringUtils.isNotEmpty(mapping.getExchangeExpress()))
                .sorted(Comparator.comparingInt(mapping -> (-StrUtil.count(mapping.getExchangeExpress(), "."))))
                .forEach(mapping->{
                    Object value = ProcessorCache.getValueProcessor(mapping.getValueProcessName()).process(JSONUtil.getByPath(json,mapping.getParamValueExpress()),mapping.getDefaultValue());
                    JSONObject jsonObject = JSONObject.parseObject(mapping.getDicConvert());
                    if(jsonObject!=null&&!jsonObject.isEmpty()){
                        value = jsonObject.get(value);
                    }
                    JSONUtil.putByPath(paramsJson, mapping.getExchangeExpress(), value);
        });
        return paramsJson;
    }

    /**
     * Convert json to map map
     *
     * @param json json
     * @return the map
     */
    public static Map<String,String> convertJsonToMap(JSON json){
        JSONObject jsonObject = JSONObject.parseObject(json.toString());
        Map<String,String> map = new HashMap<>();
        for (String key : jsonObject.keySet()) {
            map.put(key,jsonObject.getString(key));
        }
        return map;
    }

    /**
     * Convert json to object map map
     *
     * @param json json
     * @return the map
     */
    public static Map<String,Object> convertJsonToObjectMap(JSON json){
        JSONObject jsonObject = JSONObject.parseObject(json.toString());
        Map<String,Object> map = new HashMap<>();
        for (String key : jsonObject.keySet()) {
            map.put(key,jsonObject.getString(key));
        }
        return map;
    }

    /**
     * Convert out json
     *
     * @param json            json
     * @param outDataMappings out data mappings
     * @return the json
     */
    public static JSON convertOut(JSON json, List<GateWayOutDataMapping> outDataMappings,SystemType systemType){
        JSON  rtn = JSONUtil.parse("{}");
        outDataMappings
                .stream()
                .filter(mapping -> StringUtils.isNotEmpty(mapping.getExchangeExpress()))
                .sorted(Comparator.comparingInt(mapping -> (-StrUtil.count(mapping.getExchangeExpress(), "."))))
                .forEach(outDataMapping->{
                    Object responseValue = JSONUtil.getByPath(json,outDataMapping.getExchangeExpress());
                    Object value = ProcessorCache.getValueProcessor(outDataMapping.getValueProcessName()).process(JSONUtil.getByPath(json,outDataMapping.getExchangeExpress()),outDataMapping.getDefaultValue());
                    JSONObject jsonObject = JSONObject.parseObject(outDataMapping.getDicConvert());
                    if(jsonObject!=null&&!jsonObject.isEmpty()){
                        value = jsonObject.get(value);
                    }
                    JSONUtil.putByPath(rtn,outDataMapping.getApiOutExpress(),value);
                    if(CommonConstants.COMMON_YES.equalsIgnoreCase(outDataMapping.getIsDetectValue())&&StringUtils.isNotEmpty((String)responseValue)) {
                        saveDetectRecord((String)responseValue,systemType);
                    }
                });
        return  rtn;
    }

    public static void saveDetectRecord(String detectValue,SystemType systemType){
        ThreadUtil.execute(() -> {
            long count = SpringContextHolder.getBean(GateWayDetectRecordMapper.class)
                    .selectCount(Wrappers.<GateWayDetectRecord>lambdaQuery().eq(GateWayDetectRecord::getSystemType,systemType).eq(GateWayDetectRecord::getDetectValue,detectValue));
            if(count<1){
                GateWayDetectRecord gateWayDetectRecord = new GateWayDetectRecord();
                gateWayDetectRecord.setDetectValue(detectValue);
                gateWayDetectRecord.setSystemType(systemType);
                SpringContextHolder.getBean(GateWayDetectRecordMapper.class).insert(gateWayDetectRecord);
            }
        });
    }

    public static JSON genDeepRequestParam(JSON json, List<GateWaySystemParamInMapping> mappings){
        JSON paramsJson = JSONUtil.parse("{}");
        mappings.stream()
                .filter(mapping -> StringUtils.isNotEmpty(mapping.getExchangeExpress()))
                .sorted(Comparator.comparingInt(mapping -> (-StrUtil.count(mapping.getExchangeExpress(), "."))))
                .forEach(mapping->{
                    deepCopyValue(json,paramsJson,mapping.getParamValueExpress(),mapping.getExchangeExpress(),mapping.getDefaultValue(),ProcessorCache.getValueProcessor(mapping.getValueProcessName()),mapping.getDicConvert());
                });
        return paramsJson;
    }

    public static JSON deepConvert(JSON json, List<GateWayOutDataMapping> mappings){
//        JSON targetJson = JSONUtil.parse("{}");
        JSON targetJson = new cn.hutool.json.JSONObject(JSONUtil.parseObj("{}"), JSONConfig.create().setIgnoreNullValue(false));
        mappings.stream()
                .filter(mapping -> StringUtils.isNotEmpty(mapping.getExchangeExpress()))
                .sorted(Comparator.comparingInt(mapping -> (-StrUtil.count(mapping.getExchangeExpress(), "."))))
                .forEach(mapping->{
                    deepCopyValue(json,targetJson,mapping.getExchangeExpress(),mapping.getApiOutExpress(),mapping.getDefaultValue(),ProcessorCache.getValueProcessor(mapping.getValueProcessName()),mapping.getDicConvert());
                });
        return targetJson;
    }


    public static void deepConvert(JSON targetJson,JSON json, List<GateWayOutDataMapping> mappings){
        mappings.stream()
                .filter(mapping -> StringUtils.isNotEmpty(mapping.getExchangeExpress()))
                .sorted(Comparator.comparingInt(mapping -> (-StrUtil.count(mapping.getExchangeExpress(), "."))))
                .forEach(mapping->{
                    deepCopyValue(json,targetJson,mapping.getExchangeExpress(),mapping.getApiOutExpress(),mapping.getDefaultValue(),ProcessorCache.getValueProcessor(mapping.getValueProcessName()),mapping.getDicConvert());
                });
    }


    public static void deepCopyValue(JSON source, JSON target, String sourcePath, String targetPath, String defaultValue, ValueProcessor valueProcessor,String convert){
        int sourceArrayCount = StrUtil.count(sourcePath,"[*]");
        int targetArrayCount = StrUtil.count(targetPath,"[*]");
        Assert.isTrue(sourceArrayCount==targetArrayCount, MessageFormat.format("转换值表达式数组数量不匹配,取值表达式[{0}],写值表达式[{1}]",sourcePath,targetPath));
        if(sourceArrayCount==0){
            Object value = valueProcessor.process(JSONUtil.getByPath(source,sourcePath),defaultValue);
            JSONObject jsonObject = JSONObject.parseObject(convert);
            if(jsonObject!=null&&!jsonObject.isEmpty()){
                value = jsonObject.get(value);
            }
            JSONUtil.putByPath(target,targetPath,value);
        }else{
            int firstSourceArrayPos = sourcePath.indexOf("[*]");
            int firstTargetArrayPos = targetPath.indexOf("[*]");
            JSONArray sourceArray = (JSONArray) JSONUtil.getByPath(source,sourcePath.substring(0,firstSourceArrayPos));
            if(sourceArray==null){
                sourceArray = JSONUtil.createArray();
            }
            JSONArray targetArray = JSONUtil.getByPath(target,targetPath.substring(0,firstTargetArrayPos))==null?JSONUtil.createArray():((JSONArray)JSONUtil.getByPath(target,targetPath.substring(0,firstTargetArrayPos)));
            if(sourceArray.size()>targetArray.size()){
                int addNum = (sourceArray.size()-targetArray.size());
                for(int index = 0; index <addNum; index++){
//                    targetArray.add(JSONUtil.createObj());
                    targetArray.add(new cn.hutool.json.JSONObject(JSONUtil.parseObj("{}"), JSONConfig.create().setIgnoreNullValue(false)));
                }
            }
            for (int index = 0; index < sourceArray.size(); index++) {
                JSON subTarget = targetArray.getJSONObject(index);
                JSON subSource = sourceArray.getJSONObject(index);
                String subSourcePath = sourcePath.substring(firstSourceArrayPos+4);
                String subTargetPath = targetPath.substring(firstTargetArrayPos+4);
                deepCopyValue(subSource,subTarget,subSourcePath,subTargetPath,defaultValue,valueProcessor,convert);
            }
            JSONUtil.putByPath(target,targetPath.substring(0,firstTargetArrayPos),targetArray);
        }
    }
}
